using System;
using System.Text;
using Com.ContentRendering.Api.LoaderEngine.Section;
using Com.ContentRendering.Api.LoaderEngine.Exceptions;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Com.ContentRendering.Api.LoaderEngine
{
    /// <summary/>
    public class DocumentLoader
    {
        private Func<string, string> _templateLoaderDelegate;
        private PreprocessingSeparatorsInterface _preprocessingSeparators;
        private string[] _importSeparators;
        private string[] _constantFlagSeparators;

        /// <summary/>
        public DocumentLoader(
            Func<string, string> templateLoaderDelegate,
            PreprocessingSeparatorsInterface preprocessingSeparators)
        {
            this._templateLoaderDelegate = templateLoaderDelegate;
            this._preprocessingSeparators = preprocessingSeparators;

            // create this once so it is cached
            this._importSeparators = new string[] { this._preprocessingSeparators.ImportStart, this._preprocessingSeparators.ImportEnd };

            // create this once so it is cached
            this._constantFlagSeparators = new string[] 
            { 
                this._preprocessingSeparators.GetSetConstantStart, 
                this._preprocessingSeparators.GetSetConstantEnd, 
                this._preprocessingSeparators.GetSetFlagStart, 
                this._preprocessingSeparators.GetSetFlagEnd 
            };
        }

        /// <summary/>
        public string Load(string name)
        {
            // parse the imports first
            string template = this.ParseImports(name);

            // parse the constants and flags
            return this.ParseConstantsAndFlags(template);
        }

        /// <summary/>
        private string ParseImports(string name)
        {
            SectionReaders sectionReaders = new SectionReaders();

            // add the section
            sectionReaders.Add(name, new SectionReader(this.LoadImportSections(name)));

            bool importStartSection = false;
            string importNameSection = null;
            StringBuilder stringBuilder = new StringBuilder();

            while (sectionReaders.MoveNext())
            {
                // identify the section
                if (sectionReaders.Section == this._preprocessingSeparators.ImportStart)
                {
                    // if a start separator is present then fail
                    if (importStartSection)
                    {
                        throw new ImportEndSeparatorMissingException();
                    }

                    // set the flag
                    importStartSection = true;
                }
                else if (sectionReaders.Section == this._preprocessingSeparators.ImportEnd)
                {
                    // a start separator is required
                    if (!importStartSection)
                    {
                        throw new ImportStartSeparatorMissingException();
                    }

                    // a name is required
                    if (string.IsNullOrEmpty(importNameSection))
                    {
                        throw new ImportNameMissingException();
                    }

                    // circular refernce check here
                    if (sectionReaders.ContainsName(importNameSection))
                    {
                        throw new CircularReferenceException();
                    }

                    // add the section
                    sectionReaders.Add(importNameSection, new SectionReader(this.LoadImportSections(importNameSection)));

                    // reset values
                    importStartSection = false;
                    importNameSection = null;
                }
                else
                {
                    // is this after a start section
                    if (importStartSection)
                    {
                        importNameSection = sectionReaders.Section;
                    }
                    else
                    {
                        stringBuilder.Append(sectionReaders.Section);
                    }
                }
            }

            // if a start separator is present then fail
            if (importStartSection)
            {
                throw new ImportEndSeparatorMissingException();
            }

            return stringBuilder.ToString();
        }

        /// <summary/>
        private List<string> LoadImportSections(string name)
        {
            // read the template
            string template = this._templateLoaderDelegate(name);

            // split the template into sections
            return StringSplit.Split(template, this._importSeparators);
        }

        /// <summary/>
        private string ParseConstantsAndFlags(string template)
        {
            List<AbstractSection> sections = StringSplit.Split(template, this._constantFlagSeparators).ConvertAll(
                this.ConvertToSection);

            while (true)
            {
                Dictionary<string, string> constants = new Dictionary<string, string>();
                List<string> flags = new List<string>();
                int constantStartIndex = Constant.NOT_FOUND;
                int flagStartIndex = Constant.NOT_FOUND;
                StringBuilder stringBuilder = new StringBuilder();

                for (int index = 0; index < sections.Count; index++)
                {
                    // what type of object is this
                    if (typeof(ConstantSetSection).IsInstanceOfType(sections[index]))
                    {
                        ConstantSetSection constantSetSection = (ConstantSetSection)sections[index];
                        
                        // record (replace) the constant
                        constants[constantSetSection.Name] = constantSetSection.Value;
                    }
                    else if (typeof(FlagOnSection).IsInstanceOfType(sections[index]))
                    {
                        FlagOnSection flagOnSection = (FlagOnSection)sections[index];

                        // record (replace) the constant
                        flags.Add(flagOnSection.Name);
                    }
                    else if (typeof(ConstantOutSection).IsInstanceOfType(sections[index]))
                    {
                        ConstantOutSection constantOutSection = (ConstantOutSection)sections[index];

                        // always remove the item
                        sections.RemoveAt(index);

                        // does the name match
                        if (constants.ContainsKey(constantOutSection.Name))
                        {
                            // replace the item
                            sections.Insert(index, new ContentSection(constants[constantOutSection.Name]));
                        }

                        // must break to restart state
                        break;
                    }
                    else if (typeof(FlagOutSection).IsInstanceOfType(sections[index]))
                    {
                        FlagOutSection flagOutSection = (FlagOutSection)sections[index];

                        // always remove the item
                        sections.RemoveAt(index);

                        // does the name match
                        if (flags.Contains(flagOutSection.Name))
                        {
                            // replace the item
                            sections.Insert(index, new ContentSection(flagOutSection.Value));
                        }

                        // must break to restart state
                        break;
                    }
                    else if (typeof(ConstantStartSection).IsInstanceOfType(sections[index]))
                    {
                        // record the index
                        constantStartIndex = index;

                        // reset the string builder
                        stringBuilder = new StringBuilder();
                    }
                    else if (typeof(FlagStartSection).IsInstanceOfType(sections[index]))
                    {
                        // record the index
                        flagStartIndex = index;

                        // reset the string builder
                        stringBuilder = new StringBuilder();
                    }
                    else if (typeof(ConstantEndSection).IsInstanceOfType(sections[index]))
                    {
                        // check for errors
                        if (constantStartIndex == Constant.NOT_FOUND || constantStartIndex < flagStartIndex)
                        {
                            throw new ConstantStartSeparatorMissingException();
                        }

                        // check the content
                        if (stringBuilder.Length == 0)
                        {
                            throw new ConstantNameMissingException();
                        }
                        
                        // fetch the body
                        int shiftedIndex = DocumentLoader.ShiftConstantGetsAndFlagOns(
                            sections,
                            constantStartIndex,
                            index);

                        // remove the range
                        sections.RemoveRange(constantStartIndex, shiftedIndex - constantStartIndex + 1);

                        string[] nameValues = stringBuilder.ToString().Split(
                            new string[] { this._preprocessingSeparators.GetSetConstantCenter }, 
                            Constant.SPLIT_COUNT,
                            StringSplitOptions.None);
                        
                        // is this a name or name and value
                        if (nameValues.Length == 1)
                        {
                            // replace the item
                            sections.Insert(constantStartIndex, new ConstantOutSection(nameValues[0]));
                        }
                        else
                        {
                            // replace the item
                            sections.Insert(constantStartIndex, new ConstantSetSection(
                                nameValues[0],
                                nameValues[1]));
                        }

                        // must break to restart state
                        break;
                    }
                    else if (typeof(FlagEndSection).IsInstanceOfType(sections[index]))
                    {
                        // check for errors
                        if (flagStartIndex == Constant.NOT_FOUND || flagStartIndex < constantStartIndex)
                        {
                            throw new FlagStartSeparatorMissingException();
                        }

                        // check the content
                        if (stringBuilder.Length == 0)
                        {
                            throw new FlagNameMissingException();
                        }

                        // fetch the body
                        int shiftedIndex = DocumentLoader.ShiftConstantGetsAndFlagOns(
                            sections,
                            flagStartIndex,
                            index);

                        // remove the range
                        sections.RemoveRange(flagStartIndex, shiftedIndex - flagStartIndex + 1);

                        string[] nameValues = stringBuilder.ToString().Split(
                            new string[] { this._preprocessingSeparators.GetSetFlagCenter },
                            Constant.SPLIT_COUNT,
                            StringSplitOptions.None);

                        // is this a name or name and value
                        if (nameValues.Length == 1)
                        {
                            // replace the item
                            sections.Insert(flagStartIndex, new FlagOnSection(nameValues[0]));
                        }
                        else
                        {
                            // replace the item
                            sections.Insert(flagStartIndex, new FlagOutSection(
                                nameValues[0],
                                nameValues[1]));
                        }

                        // must break to restart state
                        break;
                    }
                    else if (typeof(ContentSection).IsInstanceOfType(sections[index]))
                    {
                        ContentSection contentSection = (ContentSection)sections[index];

                        // write to the output
                        stringBuilder.Append(contentSection.Value);
                    }

                    // is this the end
                    if (index == sections.Count - 1)
                    {
                        // error validation
                        if (constantStartIndex != Constant.NOT_FOUND)
                        {
                            throw new ConstantEndSeparatorMissingException();
                        }

                        // error validation
                        if (flagStartIndex != Constant.NOT_FOUND)
                        {
                            throw new FlagEndSeparatorMissingException();
                        }

                        // exit
                        return stringBuilder.ToString();
                    }
                }
            }
        }

        /// <summary/>
        private static int ShiftConstantGetsAndFlagOns(
            List<AbstractSection> sections, 
            int startIndex, 
            int endIndex)
        {
            int index = startIndex;
            int adjustment = 0;

            while (index <= endIndex - adjustment)
            {
                if (typeof(FlagOnSection).IsInstanceOfType(sections[index]) ||
                    typeof(ConstantSetSection).IsInstanceOfType(sections[index]))
                {
                    AbstractSection moveSection = sections[index];

                    // remove the old
                    sections.RemoveAt(index);
                    sections.Insert(endIndex - adjustment, moveSection);

                    // increase the adjustment
                    adjustment++;
                }

                // increase the index
                index++;
            }

            // return the adjusted end
            return endIndex - adjustment;
        }

        /// <summary/>
        private AbstractSection ConvertToSection(string section)
        {
            // check the type
            if (section == this._preprocessingSeparators.GetSetConstantStart)
            {
                return new ConstantStartSection();
            }
            else if (section == this._preprocessingSeparators.GetSetConstantEnd)
            {
                return new ConstantEndSection();
            }
            else if (section == this._preprocessingSeparators.GetSetFlagStart)
            {
                return new FlagStartSection();
            }
            else if (section == this._preprocessingSeparators.GetSetFlagEnd)
            {
                return new FlagEndSection();
            }
            else
            {
                return new ContentSection(section);
            }
        }
    }
}
